﻿using System.Data;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using OfficeOpenXml;
using OfficeOpenXml.Table;

namespace Devonline.AspNetCore;

/// <summary>
/// excel 导出操作的服务
/// </summary>
public class ExcelExportService : IExcelExportService
{
    private string _fileName;
    protected readonly ILogger<ExcelExportService> _logger;
    protected readonly ExcelPackage _excelPackage;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="context"></param>
    /// <param name="logger"></param>
    /// <param name="cache"></param>
    /// <param name="httpContextAccessor"></param>
    /// <param name="appSetting"></param>
    public ExcelExportService(ILogger<ExcelExportService> logger)
    {
        _logger = logger;
        _excelPackage = new ExcelPackage();
        _fileName = string.Empty;
    }

    /// <summary>
    /// 导出数据到 excel
    /// 需要 Data(必须), Columns(非必须) SheetName(非必须) 和 Converters(非必须) 参数
    /// </summary>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <param name="excelData">excel 数据对象</param>
    public virtual IExcelExportService Export<TEntitySet>(ExcelData<TEntitySet> excelData)
    {
        if (excelData.Data == null || (!excelData.Data.Any()))
        {
            throw new BadHttpRequestException($"excel 文件导出失败, 没有提供导出的数据!");
        }

        var attributes = excelData.GetAttributes();
        if (attributes == null || (!attributes.Any()))
        {
            throw new BadHttpRequestException($"excel 文件导出失败, 没有配置可以导入的列!");
        }

        //转换原数据
        var typeName = typeof(TEntitySet).GetDisplayName();
        excelData.SheetName ??= typeName;
        var sheet = _excelPackage.Workbook.Worksheets.Add(excelData.SheetName);
        sheet.Cells.Style.ShrinkToFit = true;

        var dataTable = ConvertData(excelData);
        var members = attributes.OrderBy(x => x.Index).Select(x => x.Field).ToArray();
        sheet.Cells.LoadFromDataTable(dataTable, true, TableStyles.None);

        //格式化列
        FormatColumn(excelData, sheet);

        //导出的文件名
        _fileName = string.IsNullOrWhiteSpace(excelData.FileName) ? excelData.SheetName + AppSettings.DEFAULT_EXCEL_FILE_EXTENSION : Path.GetFileName(excelData.FileName);
        _logger.LogInformation("The user will export {typeName} to file " + _fileName, typeName);

        return this;
    }
    /// <summary>
    /// 导出数据到 excel, 此方法会按默认参数构造 ExcelData 并导出
    /// 需要 Data(必须), Columns(非必须) 和 SheetName(非必须) 参数
    /// </summary>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <param name="Data">要导出的数据集合</param>
    public virtual IExcelExportService Export<TEntitySet>(IEnumerable<TEntitySet> Data) => Export(new ExcelData<TEntitySet> { Data = Data });
    /// <summary>
    /// 在链式循环导出时, 最后一个直接导出为文件的方法
    /// <param name="fileName">导出的 Excel 文件名, 如果为空, 则使用最后一个 ExcelData 的文件名</param>
    /// </summary>
    /// <returns></returns>
    public virtual async Task<IActionResult> ExportAsync(string? fileName = null)
    {
        var result = new FileContentResult(await _excelPackage.GetAsByteArrayAsync().ConfigureAwait(false), AppSettings.CONTENT_TYPE_OF_EXCEL) { FileDownloadName = fileName ?? _fileName };
        _excelPackage.Dispose();
        return result;
    }
    /// <summary>
    /// 单个数据集直接导出到一个 excel 文件, 并直接返回
    /// </summary>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <param name="excelData">excel 数据对象</param>
    /// <returns></returns>
    public virtual async Task<IActionResult> ExportAsync<TEntitySet>(ExcelData<TEntitySet> excelData) => await Export(excelData).ExportAsync();
    /// <summary>
    /// 导出数据到 excel, 此方法会按默认参数构造 ExcelData 并导出
    /// 需要 Data(必须), Columns(非必须) 和 SheetName(非必须) 参数
    /// </summary>
    /// <typeparam name="TEntitySet">业务数据类型</typeparam>
    /// <param name="Data">要导出的数据集合</param>
    public virtual async Task<IActionResult> ExportAsync<TEntitySet>(IEnumerable<TEntitySet> Data) => await ExportAsync(new ExcelData<TEntitySet> { Data = Data });
    /// <summary>
    /// 获取导出处理过程中存在于内存中的 excel 文件数据流
    /// </summary>
    /// <returns></returns>
    public virtual async Task<byte[]> GetContentsAsync()
    {
        var bytes = await _excelPackage.GetAsByteArrayAsync().ConfigureAwait(false);
        _excelPackage.Dispose();
        return bytes;
    }

    /// <summary>
    /// 在存在 Converters 的情况下对即将导出的值进行转换
    /// </summary>
    /// <typeparam name="TEntitySet"></typeparam>
    /// <param name="excelData"></param>
    /// <returns>转换结果</returns>
    protected virtual DataTable ConvertData<TEntitySet>(ExcelData<TEntitySet> excelData)
    {
        var dataTable = new DataTable(typeof(TEntitySet).Name + AppSettings.CHAR_UNDERLINE + KeyGenerator.GetKey().ToString());
        if (excelData.Data != null && excelData.Data.Any() && excelData.Columns != null && excelData.Columns.Any())
        {
            //columns
            foreach (var column in excelData.Columns)
            {
                var attribute = excelData.Attributes.FirstOrDefault(x => x.Field?.Name == column);
                if (attribute != null && attribute.ExportType != null)
                {
                    dataTable.Columns.Add(attribute.Name, attribute.ExportType);
                }
            }

            //rows
            var type = typeof(TEntitySet);
            foreach (var entity in excelData.Data)
            {
                var row = dataTable.NewRow();
                foreach (var column in excelData.Columns)
                {
                    var attribute = excelData.Attributes.FirstOrDefault(x => x.Field?.Name == column);
                    if (attribute != null && attribute.Field != null && (!string.IsNullOrWhiteSpace(attribute.Name)))
                    {
                        object? value = attribute.Field.GetValue(entity);
                        if (excelData.Converters?.ContainsKey(column) ?? false)
                        {
                            value = excelData.Converters[column].Invoke(entity, value);
                        }
                        else
                        {
                            //没有 Converters 的情况下, 对 bool 类型和 enum 类型进行默认转换
                            if (attribute.Field.PropertyType == typeof(bool) && value is bool boolean)
                            {
                                value = boolean ? "是" : "否";
                            }
                            else if (value != null && attribute.Field.PropertyType.IsEnum)
                            {
                                value = ((Enum)value).GetDisplayName();
                            }
                        }

                        row.SetField(attribute.Name, value);
                    }
                }

                dataTable.Rows.Add(row);
            }
        }

        return dataTable;
    }
    /// <summary>
    /// 格式化列
    /// </summary>
    /// <typeparam name="TEntitySet"></typeparam>
    /// <param name="excelData"></param>
    protected virtual void FormatColumn<TEntitySet>(ExcelData<TEntitySet> excelData, in ExcelWorksheet sheet)
    {
        var rowCount = (excelData.Data?.Count() ?? AppSettings.UNIT_ZERO) + AppSettings.UNIT_ONE;
        foreach (var attribute in excelData.Attributes)
        {
            try
            {
                //set width
                sheet.Column(attribute.Index).Width = attribute.Size;

                //set format
                if (rowCount > AppSettings.UNIT_ONE && (!string.IsNullOrWhiteSpace(attribute.Format)))
                {
                    sheet.Cells[AppSettings.UNIT_TWO, attribute.Index, rowCount, attribute.Index].Style.Numberformat.Format = attribute.Format;
                }
            }
            catch (Exception ex)
            {
                _logger.LogWarning(ex, "Excel sheet {excelSheet} column {excelColumn} set failed!", excelData.SheetName, attribute.Name);
            }
        }
    }
}